package com.example.algorithm.dp;

import java.util.Scanner;
//01背包
public class Week3 {
    //01背包 n件物品，第i件物品重量weight[i]价值value[i]
    public static void main1() {
        int n = 3;
        int bagWeight = 4;
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int[][] dp = new int[n][bagWeight + 1];

        //i表示物品、j表示背包容量
        //dp[i][j] 从下标[0-i]的物品里任意取，放进容量为j的背包，最大价值和
        for (int j = weight[0]; j <= bagWeight; j++) {
            dp[0][j] = value[0];
        }

        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= bagWeight; j++) {
                if (j < weight[i]) {
                    //不放物品i
                    dp[i][j] = dp[i - 1][j];
                } else {
                    //放物品i
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
                }
            }
        }

        System.out.println(dp[n - 1][bagWeight]);
    }

    //    滚动数组，把二维dp降为一维dp
    //    上一层可重复利用，直接拷贝到当前层。
    //    状态转移公式去掉dp[i - 1]
    public static void main2() {
        int n = 3;
        int bagWeight = 4;
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};

        // 创建一个动态规划数组 dp，初始值为 0
        int[] dp = new int[n + 1];
        for (int i = 0; i < bagWeight; i++) {
            for (int j = n; j >= weight[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }

        System.out.println(dp[n]);
    }

    //    分割等和子集，[1, 5, 11, 5]=>[1, 5, 5] 和 [11].
    //    背包的体积为sum / 2
    //    背包要放入的商品（集合的元素）重量为元素的数值，价值也为元素数值
    //    背包如果正好装满，说明找到了总和为sum / 2 的子集。
    //    背包中每一个元素是不可重复放入。
    //    求背包最多能装多少。
    public boolean canPartition(int[] nums) {
        if (nums == null || nums.length == 0) return false;
        int n = nums.length;
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        //总和为奇数，不能平分
        if (sum % 2 != 0) return false;
        int target = sum / 2;
        int[] dp = new int[target + 1];
        for (int i = 0; i < n; i++) {
            for (int j = target; j >= nums[i]; j--) {
                //物品 i 的重量是 nums[i]，其价值也是 nums[i]
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }

            //剪枝一下，每一次完成內層的for-loop，立即檢查是否dp[target] == target，優化時間複雜度（26ms -> 20ms）
            if (dp[target] == target)
                return true;
        }
        return dp[target] == target;
    }

    //    最后一块石头的重量，尽量让石头分成重量相同的两堆，相撞之后剩下的石头最小，这样就化解成01背包问题了。
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for (int i : stones) {
            sum += i;
        }
        int target = sum >> 1;
        int[] dp = new int[target + 1];
        for (int i = 0; i < stones.length; i++) {
            //采用倒序
            for (int j = target; j >= stones[i]; j--) {
                //两种情况，要么放，要么不放
                dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }
        return sum - 2 * dp[target];
    }

    //    目标和
    //    + 或 -符合最终目标和为S的方法数
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for (int i = 0; i < nums.length; i++) sum += nums[i];

        //如果target的绝对值大于sum，那么是没有方案的
        if (Math.abs(target) > sum) return 0;
        //如果(target+sum)除以2的余数不为0，也是没有方案的
        if ((target + sum) % 2 == 1) return 0;

        int bagSize = (target + sum) / 2;
        int[] dp = new int[bagSize + 1];
        dp[0] = 1;

        for (int i = 0; i < nums.length; i++) {
            for (int j = bagSize; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }

        return dp[bagSize];
    }

    //    一和零
    //二进制字符串数组 strs 和数 m 和 n，相当于两个维度的背包
    //找出并返回strs的最大子集的大小，最多 有 m 个 0 和 n 个 1
    public int findMaxForm(String[] strs, int m, int n) {
        //dp[i][j]表示i个0和j个1时的最大子集
        int[][] dp = new int[m + 1][n + 1];
        int oneNum, zeroNum;
        for (String str : strs) {
            oneNum = 0;
            zeroNum = 0;
            for (char ch : str.toCharArray()) {
                if (ch == '0') {
                    zeroNum++;
                } else {
                    oneNum++;
                }
            }
            //倒序遍历
            for (int i = m; i >= zeroNum; i--) {
                for (int j = n; j >= oneNum; j--) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[m][n];
    }
}
